package com.mycat.io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * NIO 服务端。
 * 同步非阻塞IO。
 * 数据面向-缓冲区。
 *
 * @author roger.
 * @email 190642964@qq.com
 * @create 2017-02-17
 */
public class NIOServer {

    int port;
    ServerSocketChannel server;
    Selector selector;
    ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
    ByteBuffer sendBuffer = ByteBuffer.allocate(1024);

    Map<SelectionKey, String> sessionMsg = new HashMap<>();

    public NIOServer(int port) {
        this.port = port;

        try {
            // 先修建高速公路
            server = ServerSocketChannel.open();
            System.out.println("打开ServerSocketChannel.....");

            // 关卡也打开，可以多路复用了
            server.socket().bind(new InetSocketAddress(this.port));
            // 设置为非阻塞，默认为阻塞
            server.configureBlocking(false);
            System.out.println("打开ServerSocketChannel.....绑定端口：" + this.port + ",设置为非阻塞...");

            // 管家开始营业
            selector = Selector.open();
            System.out.println("打开Selector排队管理...实现多路复用机制....");

            // 高速管家，BOSS已经准备就绪，等会又客人来了，要通知我一下
            server.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("注册Selector排队管理器到ServerSocketChannel,当有客户端连接时通知ServerSocketChannel......");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void listener() throws IOException {

        int num = 0;
        while (true) {
            num++;
            System.out.println("NIOServer服务端第【" + num + "】次从Selector排号管理器中获取是否有客户端注册......");
            // 判断当前有没有客户来注册，有没有排队的，有没有取号的
            int i = selector.select();
            System.out.println("NIOServer服务端第【" + num + "】次从Selector排号管理器中获取有【" + i + "】个客户端......");
            if (i == 0) {
                System.out.println("NIOServer服务端第【" + num + "】次从Selector排号管理器中获取有【" + i + "】个客户端......继续进行轮回获取是否有客户端来注册");
                continue;
            }

            // 获取所有的排号
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();

            System.out.println("NIOServer服务端第【" + num + "】次从Selector排号管理器中获取所有的排号......" + keys.size());

            while (iterator.hasNext()) {
                System.out.println("NIOServer服务端第【" + num + "】次从排号列表中 ---  开始处理数据........");
                // 来一个处理一个
                process(iterator.next());
                System.out.println("NIOServer服务端第【" + num + "】次从排号列表中 ---  处理数据完毕........");


                // 处理完后打发走
                System.out.println("NIOServer服务端第【" + num + "】次从排号列表中 删除已经处理的排号........");
                iterator.remove();
            }
        }

    }

    public void process(SelectionKey key) throws IOException {

        System.out.println("当前客户端：" + key.toString());
        System.out.println("key.isAcceptable() --> " + key.isAcceptable());
        System.out.println("key.isReadable() --> " + key.isReadable());
        System.out.println("key.isWritable() --> " + key.isWritable());

        // 判断客户有没有跟我们的BOSS建立好连接
        if (key.isAcceptable()) {
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
        }
        // 判断是否可以读数据
        else if (key.isReadable()) {
            SocketChannel client = (SocketChannel) key.channel();
            receiveBuffer.clear();
            int len = client.read(receiveBuffer);
            if (len > 0) {
                String msg = new String(receiveBuffer.array(), 0, len);
                sessionMsg.put(key, msg);
                System.out.println("获取客户端发送的数据：" + msg);
            }

            client.register(selector, SelectionKey.OP_WRITE);
        }
        // 判断是否可以写数据
        else if (key.isWritable()) {
            if (!sessionMsg.containsKey(key)) {
                return;
            }
            SocketChannel client = (SocketChannel) key.channel();

            sendBuffer.clear();

            sendBuffer.put(new String(sessionMsg.get(key) + ",你好，你的请求已处理").getBytes());
            sendBuffer.flip();

            client.write(sendBuffer);

            client.register(selector, SelectionKey.OP_READ);
        }
    }

    public static void main(String[] args) {
        try {
            new NIOServer(8888).listener();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
